/*
The MIT License (MIT)

Copyright (c) 2013 Bert Wierenga

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "hhdD3D11.h"
#include "hhdGfxViewportD3D11.h"
#include "hhdGfxDeviceD3D11.h"
#include "hhdGfxRenderTargetD3D11.h"
#include "hhdAssert.h"


namespace hhd
{
	GfxViewportD3D11::GfxViewportD3D11()
		: IGfxViewport()
		, _window_state(VT_Default)
		, _back_buffer(NULL)
		, _parent_gfx_device_d3d11(NULL)
		, _dxgi_swap_chain(NULL)
		, _window_handle(NULL)
	{
	}

	GfxViewportD3D11::~GfxViewportD3D11()
	{
	}

	void GfxViewportD3D11::initialize(GfxDevice* in_parent, const GfxViewportConfig& in_config)	
	{
		IGfxViewport::initialize(in_parent, in_config);
		_parent_gfx_device_d3d11 = in_parent;

		_window_state = in_config.viewport_type;

		//window
		createWindow( in_config.name,
			(WNDPROC)in_config.os_func,
			in_config.game_ptr,
			in_config.viewport_type,
			in_config.pixel_width,
			in_config.pixel_height,
			in_config.x_location,
			in_config.y_location);

		//swap chain
		createSwapChain( in_config.viewport_type == VT_Windowed ? TRUE : FALSE);

		//d3d11 textures and viewport
		acquireBackBuffer();
	}

	void GfxViewportD3D11::finalize()
	{
		releaseBackBuffer();

		destroySwapChain();

		destroyWindow();
		
		IGfxViewport::finalize();
	}

	void GfxViewportD3D11::clearBackBuffer(Color in_rgba)
	{
		_parent_gfx_device_d3d11->clearRenderTarget(_back_buffer, in_rgba);
	}
	
	void GfxViewportD3D11::present()
	{
		_dxgi_swap_chain->Present(0, 0);
	}

	void GfxViewportD3D11::createWindow(const char* in_name,
		WNDPROC in_windows_procedure,
		IGame* in_game,
		GfxViewportType in_type,
		uint32_t in_width,
		uint32_t in_height,
		uint32_t in_x_location,
		uint32_t in_y_location)
	{
		HHD_ASSERT(in_windows_procedure != NULL);

		//baw	Create the description data for the window.
		WNDCLASSEX window_class_info;

		HINSTANCE h_instance = GetModuleHandle(NULL);

		window_class_info.cbSize		= sizeof(WNDCLASSEX);
		window_class_info.style			= CS_HREDRAW | CS_VREDRAW;
		window_class_info.lpfnWndProc	= (WNDPROC)in_windows_procedure;
		window_class_info.cbClsExtra	= 0;
		window_class_info.cbWndExtra	= 0;
		window_class_info.hInstance		= h_instance;
		window_class_info.hIcon			= 0;//(HICON)in_config.icon_handle;
		window_class_info.hCursor		= (HCURSOR)LoadCursor(NULL, IDC_ARROW);//(HCURSOR)in_config.cursor_handle;
		window_class_info.hbrBackground	= (HBRUSH)COLOR_WINDOW;
		window_class_info.lpszMenuName	= NULL;
		window_class_info.lpszClassName	= TEXT("Hogshead Game");
		window_class_info.hIconSm		= 0;//(HICON)in_config.small_icon_handle;

		//baw	register the class and grab the resulting ATOM.
		ATOM window_atom = RegisterClassEx(&window_class_info);

		//baw	get the width and height determined
		int32_t desktop_width;
		int32_t desktop_height;

		if( in_type == VT_Fullscreen )
		{
			//baw	fullscreen => get the information on the desktop size (currently, settings in the future)
			desktop_width = GetSystemMetrics(SM_CXSCREEN);
			desktop_height = GetSystemMetrics(SM_CYSCREEN);
		}
		else
		{
			//baw	windowed => resize the dimensions to fit inside the window with boarders.
			RECT window_rectangle = { 0, 0, in_width, in_height };
			AdjustWindowRectEx( &window_rectangle, WS_OVERLAPPEDWINDOW, FALSE, WS_EX_LEFT);

			desktop_width = window_rectangle.right-window_rectangle.left;
			desktop_height = window_rectangle.bottom-window_rectangle.top;
		}

		//baw	Create the window.
		HWND Result = CreateWindowEx( WS_EX_LEFT,
			TEXT("Hogshead Game"),
			in_name,
			(DWORD)WS_OVERLAPPEDWINDOW,
			in_x_location,
			in_y_location,
			desktop_width,
			desktop_height,
			0,
			0,
			h_instance,
			NULL );

		HHD_ASSERT(Result);

		//set the user data pointer on the window so we can get here
		LONG_PTR user_data = SetWindowLongPtr(Result, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(in_game));
		HHD_ASSERT((IGame*)user_data == NULL);
		
		ShowWindow(Result, SW_SHOW);
		UpdateWindow(Result);

		//baw	hold on to the handle
		_window_handle = Result;
	}

	void GfxViewportD3D11::destroyWindow()
	{
		HHD_ASSERT(_window_handle != NULL);
		//baw	you'd think we'd want to destroy the window here but in fact, if we got here we've
		//		either killed the window to do it or we're closing the program.
		//BOOL hwnd_destroy_result = DestroyWindow(_window_handle);
		//HHD_ASSERT(hwnd_destroy_result != 0);
		_window_handle = NULL;
	}

	void GfxViewportD3D11::createSwapChain(flag_t in_windowed)
	{
		//baw	calculate viewport size
		RECT client_rectangle;
		GetClientRect(_window_handle, &client_rectangle);
		LONG viewport_width = client_rectangle.right - client_rectangle.left;
		LONG viewport_height = client_rectangle.bottom - client_rectangle.top;

		//baw	setup the swap chain description.
		DXGI_SWAP_CHAIN_DESC swap_chain_description;
		ZeroMemory( &swap_chain_description, sizeof(swap_chain_description) );

		swap_chain_description.BufferDesc.Width = viewport_width;
		swap_chain_description.BufferDesc.Height = viewport_height;
		swap_chain_description.BufferDesc.RefreshRate.Numerator = 60;
		swap_chain_description.BufferDesc.RefreshRate.Denominator = 1;
		swap_chain_description.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;

		// MSAA related
		swap_chain_description.SampleDesc.Count = 1;
		swap_chain_description.SampleDesc.Quality = 0;

		swap_chain_description.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
		swap_chain_description.BufferCount = 2;
		swap_chain_description.OutputWindow = (HWND)_window_handle;
		swap_chain_description.Windowed = in_windowed ? TRUE : FALSE;
		swap_chain_description.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
		swap_chain_description.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

		//baw	do the creation
		_parent_gfx_device_d3d11->createSwapChain( _dxgi_swap_chain, swap_chain_description );
	}

	void GfxViewportD3D11::destroySwapChain()
	{
		HHD_ASSERT( _dxgi_swap_chain != NULL );
		_parent_gfx_device_d3d11->destroySwapChain(_dxgi_swap_chain);
	}

	void GfxViewportD3D11::acquireBackBuffer()
	{
		HRESULT hr;

		//baw	Get the tex2d for the back buffer
		ID3D11Texture2D* back_buffer_texture = NULL;
		hr = _dxgi_swap_chain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&back_buffer_texture);
		HHD_ASSERT( hr == S_OK );
		
		//baw	create a render target view
		GfxTexture2dConfig config;
		config.format = T2DF_R8G8B8A8;
		config.mip_count = 1;
		config.width = getWidth();
		config.height = getHeight();

		_parent_gfx_device_d3d11->createRenderTargetFromTexture(config, _back_buffer, back_buffer_texture, true);
		back_buffer_texture->Release();
	}

	void GfxViewportD3D11::releaseBackBuffer()
	{
		HHD_ASSERT( _back_buffer != NULL );
		delete _back_buffer;
		_back_buffer = NULL;
	}
}